﻿#include "direct2d_1.h"

#include <cstdio>
#include <cstdlib>

#define WIN32_LEAN_AND_MEAN
// #define NOMINMAX
#include <Windows.h>
#undef DrawText


#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d2d1.lib" )
#pragma comment(lib, "dwrite.lib")


extern "C"
{
	// 在具有多显卡的硬件设备中，优先使用NVIDIA或AMD的显卡运行
	// 需要在.exe中使用
	__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
	__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0x00000001;
}

// 释放资源
template<class Interface>
inline void SafeRelease(Interface& pInterfaceToRelease) {
	if (pInterfaceToRelease != nullptr) {
		pInterfaceToRelease->Release();
		pInterfaceToRelease = nullptr;
	}
}

static D2D1App s_d2d;
extern D2D1App* g_pD2DApp = &s_d2d;

using Microsoft::WRL::ComPtr;

inline HRESULT HR_(HRESULT hr, wchar_t const* file, int line) {
    if (FAILED(hr)) {
        wchar_t msg[256]{};
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, static_cast<DWORD>(hr), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), msg, 256, NULL);
        wchar_t buf[512]{};
        swprintf(buf, 512, L"HRESULT: 0x%08X\n\tfile: %s\n\tline: %d\n\tinfo: %s", static_cast<DWORD>(hr), file, line, msg);
        MessageBoxW(NULL, buf, L"Error", MB_OK | MB_ICONERROR);
        std::exit(EXIT_FAILURE);
    }
    return hr;
}

#ifdef _DEBUG
#define HR(X) HR_(X, __FILEW__, __LINE__);
#else
#define HR(X) HRESULT(X); // , L"unknown", 0
#endif


D2D1App::D2D1App() {
}


D2D1App::~D2D1App() {
	// this->DiscardDeviceResources();
	// SafeRelease(m_pD2DFactory);
	// SafeRelease(m_pWICFactory);
	// SafeRelease(m_pDWriteFactory);
}



HRESULT D2D1App::CreateDeviceIndependentResources() {

    // 创建 DirectWrite 工厂
    HR(DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)m_pDWriteFactory.ReleaseAndGetAddressOf()
    ));

	return S_OK;
}

HRESULT D2D1App::CreateDeviceResources(HWND m_hWnd, int width, int height) {
    // get adapter
    ComPtr<IDXGIFactory2> dxgi_factory;
    ComPtr<IDXGIAdapter1> dxgi_adapter;
    UINT dxgi_create_flags = 0;
#ifdef _DEBUG
    dxgi_create_flags |= DXGI_CREATE_FACTORY_DEBUG;
#endif
    HMODULE dxgi = LoadLibraryW(L"dxgi.dll"); if (!dxgi) { std::exit(EXIT_FAILURE); }
    auto* dxgi_CreateDXGIFactory2 = (decltype(&CreateDXGIFactory2))GetProcAddress(dxgi, "CreateDXGIFactory2"); // Windows 7 not exist
    if (dxgi_CreateDXGIFactory2) {
        HR(dxgi_CreateDXGIFactory2(dxgi_create_flags, IID_PPV_ARGS(dxgi_factory.ReleaseAndGetAddressOf())));
    } else {
        std::printf("CreateDXGIFactory2 not found\n");
        HR(CreateDXGIFactory1(IID_PPV_ARGS(dxgi_factory.ReleaseAndGetAddressOf())));
    }
    dxgi_factory->EnumAdapters1(0, dxgi_adapter.ReleaseAndGetAddressOf()); // might fail
    if (!dxgi_adapter) std::printf("hardware adapter not found\n");

    // create d3d11 device
    ComPtr<ID3D11Device> d3d11_device;
    ComPtr<ID3D11DeviceContext> d3d11_device_context;
    D3D_FEATURE_LEVEL d3d_feature_level = D3D_FEATURE_LEVEL_10_0;
    UINT d3d11_create_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#ifdef _DEBUG
    d3d11_create_flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
    D3D_FEATURE_LEVEL const d3d_feature_level_list[] = {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
    };
    HR(D3D11CreateDevice(
        dxgi_adapter.Get(), dxgi_adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_WARP, NULL, // fallback to WARP adapter
        d3d11_create_flags, d3d_feature_level_list, 4, D3D11_SDK_VERSION,
        d3d11_device.ReleaseAndGetAddressOf(), &d3d_feature_level, d3d11_device_context.ReleaseAndGetAddressOf()
    ));
    ComPtr<IDXGIDevice> dxgi_device;
    d3d11_device.As(&dxgi_device);

    // create d2d1 device
    ComPtr<ID2D1Device> d2d1_device;
    HR(D2D1CreateDevice(
        dxgi_device.Get(),
        D2D1::CreationProperties(
            D2D1_THREADING_MODE_SINGLE_THREADED,
            D2D1_DEBUG_LEVEL_INFORMATION,
            D2D1_DEVICE_CONTEXT_OPTIONS_NONE),
        d2d1_device.ReleaseAndGetAddressOf()
    ));

	// ComPtr<ID2D1DeviceContext> m_pD2DDeviceContext;
    HR(d2d1_device->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE, m_pD2DDeviceContext.ReleaseAndGetAddressOf()
    ));

    m_pD2DDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE::D2D1_TEXT_ANTIALIAS_MODE_ALIASED);


    // create swap chain
    // ComPtr<IDXGISwapChain1> m_pSwapChain;
    DXGI_SWAP_CHAIN_DESC1 dxgi_swap_chain_info{};
    dxgi_swap_chain_info.Width = width;
    dxgi_swap_chain_info.Height = height;
	dxgi_swap_chain_info.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    dxgi_swap_chain_info.SampleDesc.Count = 1;
    dxgi_swap_chain_info.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    dxgi_swap_chain_info.BufferCount = 1;
    dxgi_swap_chain_info.Scaling = DXGI_SCALING_STRETCH;
    dxgi_swap_chain_info.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    dxgi_swap_chain_info.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
    HR(dxgi_factory->CreateSwapChainForHwnd(
        d3d11_device.Get(), m_hWnd, &dxgi_swap_chain_info, NULL, NULL, m_pSwapChain.ReleaseAndGetAddressOf()
    ));

    // create target
    ComPtr<ID3D11Texture2D> d3d11_texture_2d;
    HR(m_pSwapChain->GetBuffer(
        0, IID_PPV_ARGS(d3d11_texture_2d.ReleaseAndGetAddressOf())
    ));
    ComPtr<ID3D11RenderTargetView> d3d11_render_target_view;
    HR(d3d11_device->CreateRenderTargetView(
        d3d11_texture_2d.Get(), NULL, d3d11_render_target_view.ReleaseAndGetAddressOf()
    ));
    d3d11_texture_2d.Reset();

    ComPtr<IDXGISurface> dxgi_surface;
    HR(m_pSwapChain->GetBuffer(
        0, IID_PPV_ARGS(dxgi_surface.ReleaseAndGetAddressOf())
    ));
    // ComPtr<ID2D1Bitmap1> d2d1_bitmap_target;
    HR(m_pD2DDeviceContext->CreateBitmapFromDxgiSurface(
        dxgi_surface.Get(),
        D2D1::BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            D2D1::PixelFormat(dxgi_swap_chain_info.Format, D2D1_ALPHA_MODE_PREMULTIPLIED)),
		m_pD2DTargetBimtap.ReleaseAndGetAddressOf()
    ));


//     HR(m_pD2DDeviceContext->CreateBitmapFromDxgiSurface(
//         dxgi_surface.Get(),
//         D2D1::BitmapProperties1(
//             D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
//             D2D1::PixelFormat(dxgi_swap_chain_info.Format, D2D1_ALPHA_MODE_PREMULTIPLIED)),
//         m_pD2DTargetBimtap4font.ReleaseAndGetAddressOf()
//     ));

    dxgi_surface.Reset();

//    m_pD2DDeviceContext->SetTarget(m_pD2DTargetBimtap.Get());

	return S_OK;
}

// 丢弃设备相关资源
void D2D1App::DiscardDeviceResources() {
	SafeRelease(m_pD2DDeviceContext);
	// SafeRelease(m_pD2DDevice);
	// SafeRelease(m_pD3DDevice);
	SafeRelease(m_pD2DTargetBimtap);
    // SafeRelease(m_pD2DTargetBimtap4font);
    SafeRelease(m_pSwapChain);
}


LRESULT D2D1App::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
	LRESULT result = 0;

	if (message == WM_CREATE) {
		LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
		D2D1App* pD2DApp = (D2D1App*)pcs->lpCreateParams;

		::SetWindowLongPtrW(
			hwnd,
			GWLP_USERDATA,
			PtrToUlong(pD2DApp)
		);

		result = 1;
	} else {
		D2D1App* pD2DApp = reinterpret_cast<D2D1App*>(static_cast<LONG_PTR>(
			::GetWindowLongPtrW(
				hwnd,
				GWLP_USERDATA
			)));

		bool wasHandled = false;

		if (pD2DApp) {
			switch (message) {
			case WM_LBUTTONDOWN:
			case WM_MBUTTONDOWN:
			case WM_RBUTTONDOWN:
				pD2DApp->OnMouseDown(wParam, LOWORD(lParam), HIWORD(lParam));
				break;

			case WM_SIZE:
				// pD2DApp->OnResize(LOWORD(lParam), HIWORD(lParam));
				break;

			case WM_LBUTTONUP:
			case WM_MBUTTONUP:
			case WM_RBUTTONUP:
				pD2DApp->OnMouseUp(wParam, LOWORD(lParam), HIWORD(lParam));
				break;

			case WM_MOUSEMOVE:
				pD2DApp->OnMouseMove(wParam, LOWORD(lParam), HIWORD(lParam));
				break;

			case WM_MOUSEWHEEL:
				pD2DApp->OnMouseWheel(LOWORD(wParam), HIWORD(wParam), LOWORD(lParam), HIWORD(lParam));
				break;

			case WM_DESTROY:
			{
				// pD2DApp->OnDestroy();
				PostQuitMessage(0);
			}
			result = 1;
			wasHandled = true;
			break;
			}
		}

		if (!wasHandled) {
			result = DefWindowProc(hwnd, message, wParam, lParam);
		}
	}

	return result;
}